Перейти к основному содержимому

7.07. Инъекции

Разработчику Инженеру

Инъекции

Инъекции — одна из наиболее фундаментальных и широко распространённых уязвимостей в сфере информационной безопасности. Под инъекцией понимается внедрение злоумышленником несанкционированного кода или данных в программную систему таким образом, что система интерпретирует или исполняет их как часть своей логики. Это приводит к нарушению целостности, конфиденциальности и доступности информации и ресурсов.

В основе инъекционных атак лежит недостаточная проверка, фильтрация или экранирование входных данных, предоставляемых пользователем. Злоумышленник, используя слабые места в архитектуре приложения, может манипулировать его поведением, получать доступ к конфиденциальным данным, выполнять произвольные команды на сервере, подменять контент или полностью компрометировать систему.

Инъекции не являются атакой конкретного типа — это общий паттерн уязвимости, проявляющийся в различных контекстах: базы данных, командные оболочки, языки разметки, динамические языки программирования и т.д. Ниже будут рассмотрены наиболее значимые виды инъекций, их механизмы, последствия и методы защиты.


Общая модель инъекции

Любая инъекция предполагает три основных компонента:

  1. Контекст исполнения — среда, в которой интерпретируются или исполняются входные данные (например, SQL-интерпретатор, командная оболочка, JavaScript-движок браузера).
  2. Данные, контролируемые атакующим — строка, отправляемая пользователем (через параметры URL, формы, заголовки HTTP и др.), которая затем встраивается в исполняемый код без надлежащей обработки.
  3. Нарушение синтаксической или семантической границы — злоумышленник вводит специальные символы или конструкции, которые изменяют исходную логику построения запроса или команды.

Успешная инъекция происходит тогда, когда приложение не разделяет данные и код, позволяя атакующему «переписать» поведение системы с помощью внешнего ввода.


SQL-инъекции (SQL Injection, SQLi)

SQL-инъекция — наиболее известный и исторически значимый тип инъекции. Она возникает при некорректной передаче пользовательских данных в SQL-запросы, что позволяет атакующему изменить логику запроса к реляционной базе данных.

Механизм атаки

Рассмотрим простой пример на языке C# (аналогично проявляется в PHP, Java и других языках):

string query = "SELECT * FROM Users WHERE Login = '" + userInput + "'";

Если userInput содержит строку ' OR '1'='1, то результирующий запрос примет вид:

SELECT * FROM Users WHERE Login = '' OR '1'='1'

Это приведёт к выборке всех записей из таблицы Users, поскольку условие '1'='1' всегда истинно. Злоумышленник может не только обойти аутентификацию, но и модифицировать данные (INSERT, UPDATE, DELETE), извлекать данные из других таблиц (UNION SELECT), выполнять административные команды (в случае расширенных прав СУБД) или даже получить доступ к файловой системе.

Уровни угрозы

SQL-инъекции подразделяются на:

  • Классические (In-band): данные извлекаются через тот же канал, по которому отправляется инъекция (например, вывод ошибки или через UNION).
  • Слепые (Blind): приложение не возвращает прямых данных, но реакция на истинные/ложные условия позволяет по битам извлекать информацию.
  • Out-of-band: данные передаются через сторонние каналы (например, DNS- или HTTP-запросы), если есть сетевой доступ от СУБД.

Защита от SQLi

Эффективная защита строится на нескольких уровнях:

  • Параметризованные запросы (Prepared Statements): гарантируют строгое разделение между кодом и данными. Значения передаются отдельно от текста запроса и интерпретируются исключительно как данные.
  • Экранирование специальных символов: менее надёжный подход, подвержен ошибкам и зависит от контекста (СУБД, кодировки).
  • Принцип наименьших привилегий: учётная запись приложения в СУБД должна иметь минимально необходимые права (без DROP, FILE, xp_cmdshell и т.п.).
  • Статический и динамический анализ кода: обнаружение потенциально уязвимых паттернов на этапе разработки и тестирования.

Инъекции команд операционной системы (Command Injection, CMDi)

Command Injection возникает, когда веб-приложение или программное обеспечение передаёт непроверенные пользовательские данные в системные команды (например, через system(), exec(), Process.Start() и аналоги).

Пример уязвимости

import os
os.system("ping -c 1 " + user_input)

Если user_input содержит 8.8.8.8; cat /etc/passwd, то будет выполнен не только ping, но и команда чтения файла паролей. Символы ;, &&, |, \n позволяют атакующему конструировать произвольные последовательности команд.

Контекст уязвимости

Особенно опасны такие инъекции на серверах с высокими привилегиями, где приложение запущено от имени root или SYSTEM. Последствия — полный контроль над сервером, установка бэкдоров, эксплуатация внутренней сети.

Защита

  • Избегать вызова системных команд на основе пользовательского ввода.
  • Если вызов необходим — использовать белый список допустимых значений.
  • Применять строгую валидацию и экранирование (но надёжнее — отказаться от динамического построения команд).
  • Запускать приложения в изолированных средах (контейнеры, chroot, sandbox).

Межсайтовый скриптинг (Cross-Site Scripting, XSS)

XSS — это форма инъекции, при которой вредоносный JavaScript-код внедряется в веб-страницу, отображаемую в браузере жертвы. В отличие от SQLi и CMDi, XSS напрямую не атакует сервер, а эксплуатирует доверие пользователя к сайту.

Типы XSS

  1. Отражённый (Reflected): вредоносный скрипт передаётся через URL (например, параметр поиска) и сразу возвращается в ответе сервера. Атака требует, чтобы жертва перешла по специально подготовленной ссылке.
  2. Сохранённый (Stored): скрипт сохраняется на сервере (в комментариях, профилях, форумах) и отдаётся всем пользователям, просматривающим заражённый контент.
  3. DOM-based: уязвимость возникает на стороне клиента — вредоносные данные модифицируют DOM-дерево без участия сервера (например, через location.hash, document.write).

Последствия

  • Кража куков сессии (включая HttpOnly в некоторых случаях).
  • Перенаправление на фишинговые страницы.
  • Логгирование нажатий клавиш (keylogging).
  • Выполнение действий от имени пользователя (например, перевод средств).

Защита

  • Экранирование контента: перед вставкой в HTML все специальные символы (<, >, ", ', &) должны быть заменены на HTML-сущности (&lt;, &gt; и т.д.).
  • Контекстно-зависимая обработка: правила экранирования различаются для HTML, атрибутов, JavaScript, CSS, URL.
  • Content Security Policy (CSP): механизм, ограничивающий источники загрузки скриптов, стилей и других ресурсов.
  • Фреймворковые средства: современные фреймворки (React, Angular, Vue) по умолчанию экранируют вывод, но ручные манипуляции (innerHTML, dangerouslySetInnerHTML) могут обойти защиту.

Обобщённые принципы защиты от инъекций

Несмотря на различия в контекстах, все инъекции подчиняются общим принципам предотвращения:

  1. Разделение кода и данных: никогда не встраивайте пользовательские данные непосредственно в исполняемый код.
  2. Белые списки: вместо попыток заблокировать «плохие» символы — разрешайте только известные безопасные значения.
  3. Экранирование в соответствии с контекстом: если встраивание неизбежно, применяйте контекстно-зависимое экранирование, соответствующее целевой среде (SQL, OS, HTML и пр.).
  4. Минимизация поверхности атаки: отключайте ненужные функции (например, xp_cmdshell в MS SQL, exec в PHP), ограничивайте права, используйте沙箱.
  5. Тестирование и мониторинг: регулярное проведение тестов на проникновение, статический анализ кода, логирование подозрительных запросов.

Редкие, но критичные формы инъекций

Помимо широко известных SQL-, командных и XSS-инъекций, существует ряд специализированных форм внедрения вредоносного кода, возникающих в узкоспециализированных средах: XML-процессоры, LDAP-каталоги, XPath-выражения, NoSQL-базы данных и др. Эти уязвимости часто остаются вне поля зрения разработчиков, поскольку подсистемы, в которых они возникают, считаются «внутренними» или «некритичными». Однако в современных распределённых архитектурах такие компоненты нередко становятся точками входа для атак.


XPath-инъекции

XPath (XML Path Language) — язык запросов к XML-документам, аналогичный SQL для реляционных баз. Если веб-приложение строит XPath-выражения на основе непроверенного пользовательского ввода, возможно внедрение произвольной логики.

Пример

Предположим, приложение ищет пользователя в XML-файле:

<users>
<user>
<login>admin</login>
<password>secret</password>
</user>
</users>

XPath-запрос может выглядеть так:

string xpath = "/users/user[login='" + login + "' and password='" + password + "']";

Если злоумышленник введёт в поле логина ' or '1'='1, то запрос примет вид:

/users/user[login='' or '1'='1' and password='...']

Если система не проверяет количество возвращённых узлов, а считывает первый попавшийся — аутентификация будет обойдена. В отличие от SQL, XPath не имеет встроенных механизмов ограничения прав, поэтому уязвимость позволяет извлекать любые данные из всего XML-документа, включая поля, не предназначенные для отображения.

Защита

  • Использовать параметризованные XPath-выражения, если они поддерживаются (редко).
  • Применять строгую валидацию входных данных (белый список допустимых символов).
  • Избегать построения XPath-запросов на основе прямого пользовательского ввода.

LDAP-инъекции

LDAP (Lightweight Directory Access Protocol) — протокол для доступа к иерархическим каталогам (например, Active Directory). LDAP-инъекция возникает при некорректной обработке входных данных в поисковых фильтрах.

Пример

Фильтр может строиться так:

filter = "(uid=" + username + ")"

Если username содержит *)(uid=*))(|(uid=*, то фильтр превращается в:

(uid=*)(uid=*))(|(uid=*

Это может привести к обходу аутентификации или получению списка всех пользователей.

Особенности

LDAP использует собственный набор метасимволов: *, (, ), \, &, |, !. Их экранирование регламентировано в RFC 4515: каждый из этих символов должен быть заменён на \xx (шестнадцатеричное представление ASCII-кода).

Защита

  • Экранирование всех метасимволов в соответствии с RFC 4515.
  • Использование готовых библиотек для формирования фильтров (например, ldap.filter.escape в Python).
  • Применение строгой валидации (например, логин должен соответствовать регулярному выражению ^[a-zA-Z0-9_]+$).

NoSQL-инъекции

NoSQL-инъекции не являются прямым аналогом SQL-инъекций, поскольку большинство NoSQL-баз (MongoDB, CouchDB, Redis и др.) не используют строковые запросы в традиционном смысле. Вместо этого они принимают структурированные запросы — чаще всего в виде JSON-объектов или словарей. Уязвимость возникает при некорректной обработке пользовательских данных, которые интерпретируются как часть структуры запроса, а не как простые значения.

Пример (MongoDB)

Предположим, в приложении на Node.js происходит поиск по коллекции:

db.users.findOne({ username: req.body.username, password: req.body.password });

Если злоумышленник отправит JSON:

{
"username": { "$ne": "" },
"password": { "$ne": "" }
}

То MongoDB интерпретирует это как:

{ username: { $ne: "" }, password: { $ne: "" } }

Оператор $ne (not equal) означает «любое значение, кроме пустой строки». Таким образом, будет найден первый пользователь с непустыми логином и паролем — аутентификация обойдена.

В более сложных сценариях возможно использование операторов $where (выполнение JavaScript-кода на сервере MongoDB) или $regex для перебора данных побитно (аналог blind SQLi).

Защита

  • Строгая типизация входных данных: если ожидается строка — принимать только строки, отклоняя объекты.
  • Валидация структуры запроса: использование схем (например, Joi, Zod) для фильтрации и нормализации входных данных.
  • Отключение опасных операторов: в MongoDB можно запретить использование $where, $mapReduce и других функций на уровне базы.
  • Принцип наименьших привилегий: учётная запись приложения не должна иметь прав на выполнение административных команд.

Инъекции в динамические языки (eval-инъекции)

Многие скриптовые языки (JavaScript, Python, PHP, Ruby) поддерживают функции динамической интерпретации кода: eval(), exec(), Function(), import(), pickle.loads() и др. Использование таких функций с непроверенными данными представляет собой прямую инъекцию исполняемого кода.

Пример

user_input = request.GET['data']
result = eval(user_input)

Если user_input содержит __import__('os').system('rm -rf /'), последствия могут быть катастрофическими.

Особенности

Такие инъекции особенно опасны в средах с высокими привилегиями (например, административных панелях, внутренних инструментах). Часто разработчики считают, что «ввод контролируется», но забывают о возможностях сериализованных объектов (например, pickle в Python), которые при десериализации могут выполнять произвольный код.

Защита

  • Категорический отказ от eval() и аналогов в production-коде.
  • Использование безопасных альтернатив: JSON вместо eval для парсинга, шаблонизаторов вместо конкатенации кода.
  • При необходимости десериализации — использовать только доверенные форматы (JSON, XML с отключёнными DTD) и избегать собственных сериализованных объектов.
  • Изоляция среды выполнения (например, запуск в контейнере без доступа к файловой системе).

Объединяющий взгляд: модель «доверенной границы»

Все инъекции можно рассматривать через призму нарушения доверенной границы между данными и кодом. Приложение получает данные от внешнего агента (пользователя, API, файла) и передаёт их в интерпретатор (SQL, JS, OS shell и т.д.). Если граница между данными и кодом не защищена — интерпретатор не может отличить легитимные данные от вредоносных инструкций.

Фундаментальный принцип защиты — никогда не доверять внешним данным. Это означает, что:

  • Все входные данные должны считаться потенциально вредоносными до тех пор, пока не доказано обратное.
  • Построение исполняемой логики должно быть полностью отделено от передачи значений.
  • Контекст интерпретации должен быть максимально ограничен и изолирован.

Практические методы выявления и тестирования инъекций

Теоретическое понимание механизма инъекций необходимо, но недостаточно для обеспечения безопасности программных систем. Обнаружение уязвимостей требует системного подхода, сочетающего автоматизированные инструменты, ручной анализ и понимание контекста работы приложения.

1. Ручное тестирование (Manual Testing)

Ручное тестирование остаётся наиболее надёжным методом выявления сложных и контекстно-зависимых инъекций. Оно предполагает:

  • Анализ всех точек ввода: не только формы и URL-параметры, но и HTTP-заголовки (User-Agent, Referer), cookie, тела запросов (включая JSON, XML), файлы, веб-сокеты.
  • Фаззинг входных данных: подстановка специальных последовательностей, характерных для разных типов инъекций:
    • Для SQLi: ', ", --, /*, UNION SELECT, SLEEP(), BENCHMARK().
    • Для CMDi: ;, &, |, $(...), `...`, %0a, &&, ||.
    • Для XSS: <script>, javascript:, onerror=, "><svg onload=...>.
    • Для NoSQL: { "$ne": 1 }, { "$gt": "" }, { "$where": "..." }.
  • Наблюдение за поведением приложения: изменения в ответе, задержки (time-based blind), ошибки (error-based), перенаправления — всё это может быть индикатором уязвимости.
  • Использование прокси-инструментов: Burp Suite, OWASP ZAP позволяют перехватывать, модифицировать и повторно отправлять запросы, что критично для тестирования инъекций.

2. Автоматизированное сканирование

Автоматизированные сканеры уязвимостей (DAST — Dynamic Application Security Testing) могут быстро выявить классические инъекции, особенно отражённые формы XSS и SQLi. Однако они имеют ограничения:

  • Сложно обнаруживают слепые (blind) инъекции без чёткой сигнализации.
  • Часто дают ложные срабатывания (false positives) или пропускают уязвимости в нестандартных контекстах (например, GraphQL, WebSocket, SOAP).
  • Не понимают бизнес-логику приложения, что делает их беспомощными против логических уязвимостей.

Тем не менее, такие инструменты, как sqlmap (для SQLi), NoSQLMap (для NoSQL), dalfox (для XSS), являются мощными утилитами в руках специалиста и позволяют автоматизировать эксплуатацию подтверждённых уязвимостей.

3. Статический анализ кода (SAST)

SAST-инструменты анализируют исходный код или байт-код на наличие опасных паттернов:

  • Конкатенация строк с пользовательским вводом перед передачей в SqlCommand, os.system, eval, innerHTML.
  • Использование устаревших или небезопасных API.
  • Отсутствие вызовов функций экранирования.

Примеры инструментов: SonarQube, Checkmarx, Semgrep, Bandit (для Python), ESLint с плагинами безопасности (для JS/TS).

Важно: SAST не гарантирует отсутствие уязвимостей, но позволяет выявить рискованные участки кода на ранних этапах разработки.

4. Тестирование в CI/CD

Интеграция проверок на инъекции в процесс непрерывной интеграции позволяет выявлять регрессии и новые уязвимости до попадания кода в production. Это включает:

  • Запуск линтеров и SAST-сканеров при каждом коммите.
  • Автоматизированные unit- и интеграционные тесты с вредоносными входными данными.
  • Использование «загрязнённых» тестовых данных (taint analysis) для отслеживания путей передачи непроверенного ввода.

Стандарты и классификации

Инъекции официально признаны одной из самых критичных уязвимостей. В OWASP Top 10 инъекции (в широком смысле) неоднократно занимали первое место:

  • OWASP Top 10 2013, 2017: Injection (A1).
  • OWASP Top 10 2021: хотя формально вытеснены более широкими категориями (например, «Software and Data Integrity Failures»), инъекции по-прежнему входят в основные векторы атак.

В системе CWE (Common Weakness Enumeration) инъекции представлены множеством записей:

  • CWE-79 — XSS,
  • CWE-89 — SQL Injection,
  • CWE-78 — OS Command Injection,
  • CWE-90 — LDAP Injection,
  • CWE-611 — XML External Entity (XXE), близкая по сути,
  • CWE-94 — Code Injection.

Эти идентификаторы используются в отчётах, стандартах сертификации (PCI DSS, ISO/IEC 27001) и системах управления уязвимостями.


Архитектурные подходы к предотвращению инъекций

Помимо кодовых практик, важно внедрять архитектурные меры:

  1. Zero Trust к входным данным: любые данные извне (включая внутренние микросервисы) рассматриваются как недоверенные.
  2. Изоляция компонентов: базы данных, файловые системы, командные оболочки должны быть недоступны напрямую из публичных интерфейсов.
  3. API-гейты и WAF: Web Application Firewall может блокировать известные сигнатуры инъекций, но не заменяет корректную реализацию. Лучше использовать его как дополнительный барьер.
  4. Политики выполнения: например, в Node.js можно использовать vm2 вместо eval, в .NET — AppDomain с ограниченными разрешениями (хотя в .NET Core/AppDomain упразднён, требуется другой подход — например, изоляция в контейнерах).